1 module assert_that;
2 
3 
4 mixin template assertThat(string name, alias lhs, alias matcher)
5 {
6     int VARIABLE_FOR_ASSERT_THAT_DONT_REFER_ME = ()
7     {
8         import std.format : format;
9 
10         mixin("auto %s = %s;".format(name, lhs));
11         mixin assertThat!(name, matcher);
12 
13         return 0;
14     }();
15 }
16 
17 mixin template assertThat(string lhs, alias matcher)
18 {
19     int VARIABLE_FOR_ASSERT_THAT_DONT_REFER_ME = ()
20     {
21         import std.conv : to;
22         import std.string : join;
23 
24         mixin matcher.match!(lhs, matcher.args);
25 
26         return 0;
27     }();
28 }
29 
30 
31 template op(string operator, alias rhs, string file = __FILE__, ulong line = __LINE__)
32 {
33     import std.meta : AliasSeq;
34     alias args = AliasSeq!(operator, rhs, file, line);
35 
36     mixin template match(string lhs, string operator, alias rhs, string file, ulong line)
37     {
38         int VARIABLE_FOR_ASSERT_THAT_DONT_REFER_ME = delegate int()
39         {
40             import std.format : format;
41 
42             static if (__traits(compiles, !mixin(lhs ~ operator ~ rhs.stringof)))
43             {
44                 import std.conv : to;
45 
46                 mixin(
47                     "#line %d \"%s\"\n".format(line, file) ~
48                     q{assert(mixin(lhs ~ operator ~ rhs.stringof), lhs ~ ": actual " ~ mixin(lhs).to!string ~ ": expected " ~ operator ~ " " ~ rhs.stringof);}
49                 );
50 
51                 return 0;
52             }
53             else
54             {
55                 mixin(
56                     "#line %d \"%s\"\n".format(line, file) ~
57                     q{assert(false, lhs ~ ": invalid expression: " ~ lhs ~ " " ~ operator ~ " " ~ rhs.stringof);}
58                 );
59             }
60         }();
61     }
62 }
63 
64 template eq(alias rhs, string file = __FILE__, ulong line = __LINE__)
65 {
66     alias eq = op!("==", rhs, file, line);
67 }
68 
69 
70 template array(string file = __FILE__, ulong line = __LINE__)
71 {
72     template _(matchers...)
73     {
74         import std.meta : AliasSeq;
75         alias args = AliasSeq!(file, line, matchers);
76 
77         mixin template match(string lhs, string file, ulong line, matchers...)
78         {
79             int VARIABLE_FOR_ASSERT_THAT_DONT_REFER_ME = ()
80             {
81                 import assert_that : eq;
82 
83                 mixin eq!0.match!(lhs ~ ".length", "==", matchers.length, file, line);
84 
85                 foreach (i, matcher; matchers)
86                 {
87                     import std.conv : to;
88 
89                     mixin matcher.match!(lhs ~ "[" ~ i.to!string ~  "]", matcher.args);
90                 }
91 
92                 return 0;
93             }();
94         }
95     }
96 }
97 
98 
99 template field(string fieldName, alias matcher, string file = __FILE__, ulong line = __LINE__)
100 {
101     import std.meta : AliasSeq;
102     alias args = AliasSeq!(fieldName, matcher, file, line);
103 
104     mixin template match(string lhs, string fieldName, alias matcher, string file, ulong line)
105     {
106         int VARIABLE_FOR_ASSERT_THAT_DONT_REFER_ME = ()
107         {
108             import std.format : format;
109 
110             static if (__traits(compiles, mixin(lhs ~ "." ~ fieldName)))
111                 mixin matcher.match!(lhs ~ "." ~ fieldName, matcher.args);
112             else
113                 mixin(
114                     "#line %d \"%s\"\n".format(line, file) ~
115                     q{assert(false, lhs ~ "." ~ fieldName ~ ": field '" ~ fieldName ~ "' does not exist");}
116                 );
117 
118             return 0;
119         }();
120     }
121 }
122 
123 
124 template fields(string file = __FILE__, ulong line = __LINE__)
125 {
126     template _(matchers...)
127     {
128         import std.meta : AliasSeq;
129         alias args = AliasSeq!(file, line, matchers);
130 
131         mixin template match(string lhs, string file, ulong line, matchers...)
132         {
133             int VARIABLE_FOR_ASSERT_THAT_DONT_REFER_ME = ()
134             {
135                 import std.traits : FieldNameTuple;
136                 import assert_that : eq;
137 
138                 mixin eq!0.match!("FieldNameTuple!(typeof(" ~ lhs ~ ")).length", "==", matchers.length, file, line);
139 
140                 foreach (i, field; FieldNameTuple!(typeof(mixin(lhs))))
141                 {
142                     import std.traits : Alias;
143                     import std.conv : to;
144 
145                     mixin Alias!(matchers[i]).match!(lhs ~ "." ~ field, matchers[i].args);
146                 }
147 
148                 return 0;
149             }();
150         }
151     }
152 }
153 
154 
155 template all(matchers...)
156 {
157     alias args = matchers;
158 
159     mixin template match(string lhs, matchers...)
160     {
161         int VARIABLE_FOR_ASSERT_THAT_DONT_REFER_ME = ()
162         {
163             foreach (matcher; matchers)
164             {
165                 mixin matcher.match!(lhs, matcher.args);
166             }
167 
168             return 0;
169         }();
170     }
171 }